import { isArray, isIntegerKey } from "@vue/shared";
import { TriggerOrTypes } from "./operators";

interface IOptions {
  lazy?: Boolean,
  scheduler?: (effect: () => void) => void
}

/**
 * 
 * @param fn 用户使用 effect 时的回调函数
 * @param options 用户配置参数
 * @returns 
 */
export function effect(fn, options: IOptions = {}) {

  // 一个属性对应一个响应式的 effect
  const effectSelf = createReactiveEffect(fn, options);

  if (!options.lazy) {
    effectSelf(); // 执行用户的回调函数
  }

  return effectSelf;
}

let uid = 0;
let activeEffect; // 存放当前的 effect
const effectStack = []; // 不断的调用 effect 就不断的入栈, 本次 effect 方法执行完就出栈, 保证每次都是对应的属性和对应的 effect 依赖

/**
 * 
 * @param fn 用户使用 effect 时的回调函数
 * @param options 用户配置参数
 * @returns 
 */
function createReactiveEffect (fn, options: any = {}) {

  const effect = function reactiveEffect () {
    /**
     * effect(() => {
     *  state.age++; // 会死循环,需要判断 effectStack 中是否已经存在,
     * })
     */
    if (!effectStack.includes(effect)) {
      /**
       * 栈型结构 effectStack 来解决
       * effect(() => { 
       *  state.name -> effect1    effectStack = [effect1]
       *  effect(() => {
       *    state.age -> effect2
       *  })
       *  state.address -> effect3
       * })
       */
      try {
        // 入栈
        effectStack.push(effect); // 执行一次 effect  就保存一下
        activeEffect = effect; // 保存当前的 effect 依赖
        return fn(); // 取值时,执行 get
      } finally {
        // 执行玩一个 effect 方法后,就出栈
        effectStack.pop();
        activeEffect = effectStack[effectStack.length - 1]; // activeEffect 保存上一个的 effect 依赖
      }
    }
  }

  effect.id = uid++; // 唯一 id 标识，在 scheduler.ts 中有用到
  effect._isEffect = true;
  effect.raw = fn; // 用户的回调函数
  effect.options = options; // 用户的配置参数

  return effect;
}

// 存放收集依赖的容器
const targetMap = new WeakMap();
/**
 * 依赖收集
 * 通过 get 来调用
 * 把属性和 effect 对应起来, 做依赖收集
 * 哪些 type 类型的属性需要做依赖收集
 * @param target 对象
 * @param type get
 * @param key 属性
 */
export function track(target, type, key) {
  // console.log('track', target, key, activeEffect)
  // {name: "tom"} => name => [effect, effect, ...] 
  
  // 判断 effect 中的属性是否在 activeEffect 中, 如果不再 effect 中使用, 就不用收集
  if (activeEffect === undefined) {
    return;
  }

  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }

  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
  }
  // console.log('targetMap', targetMap);
}

/**
 * 依赖触发
 * 根据已经收集好的依赖,如果数据变化,就更新视图
 * 通过 set 来调用
 * @param target 对象
 * @param type set
 * @param key 属性
 * @param newV 用户设置的新值
 * @param oldV 旧值
 */
export function trigger (target, type, key?, newV?, oldV?) {
  // console.log('trigger', target, type, key, newV, oldV)

  const depsMap = targetMap.get(target);
  // 如果该属性没有收集过依赖 effect,就不需要做任何操作
  if (!depsMap)  return;

  // 存储所有的 effect 容器
  const effects = new Set();
  const add = effectsToAdd => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        effects.add(effect);
      });
    }
  };
  // 将所有要执行的依赖 effect 全部存放到一个新的集合中，最终一起执行

  // 判断修改了数组的长度
  if (key === 'length' && isArray(target)) {
    // 如果数组中对应的长度存在，就需要更新视图
    depsMap.forEach(dep => {
      if (key === 'length' || key > newV) {
        // 如果更改的长度小于搜集到的缩影，那么这个索引也需要触发 effect 重新执行 
        add(dep);
      }
    }); 
  } else {
    // 修改了对象
    if (key !== undefined) {
      add(depsMap.get(key));
    }
    // console.log(type, TriggerOrTypes.ADD)

    if (type === TriggerOrTypes.ADD) {
      // 如果事数组，并且属性是数字，表明是索引
      if (isArray(target) && isIntegerKey(key)) {
        // 修改了超过原数组的长度的某个索引对应的数据，比如：原始数组是 const arr = [1, 2, 3]; arr[100] = 33
        // 修改了数组中的某一索引, 如果添加了索引就对该索引添加依赖触发更新
        add(depsMap.get('length'));
      }
    }
  }

  // 执行（触发） effect 
  effects.forEach((e: any) => {
    if (e.options.scheduler) {
      e.options.scheduler(e);
    } else {
      e();
    }
  });
}

/*
1.effect 中的所用属性都会收集对应的effect  => track
2，这个属性值发生变化，就会重新执行effect  => trigger
*/